#include "./index.hpp"
#include "./util.hpp"

#include <algorithm>
#include <jsoncpp/json/json.h>


class Searcher{
private:
    Index *index;
public:
    void InitSearcher(const std::string &input_path,
                      bool force_build = false, 
                      const std::string &forward_index_path  = "./data/index/forward.index",
                      const std::string &inverted_index_path = "./data/index/inverted.index"){
        index = Index::getInstance();
        int hit_cnt = 0;

        if(!force_build){
            if(util_ns::FileUtil::existsFile(forward_index_path)){
                NORM_LOG("Hit file: %s. loading...\n", forward_index_path.c_str());
                if(index->LoadForwardIndex(forward_index_path)){
                    ++hit_cnt;
                    NORM_LOG("Load ForwardIndex Success!\n");
                }
                else {
                    NORM_LOG("Load ForwardIndex Failed!\n");
                }
            }else{
                NORM_LOG("File: %s Not Found.\n", forward_index_path.c_str());
            }
            if(util_ns::FileUtil::existsFile(inverted_index_path)){
                NORM_LOG("Hit file: %s. loading...\n", inverted_index_path.c_str());
                if(index->LoadInvertedIndex(inverted_index_path)){
                    ++hit_cnt;
                    NORM_LOG("Load InvertedIndex Success!\n");
                }
                else {
                    NORM_LOG("Load InvertedIndex Failed!\n");
                }
            }else{
                NORM_LOG("File: %s Not Found.\n", inverted_index_path.c_str());
            }
            if(hit_cnt == 2){
                NORM_LOG("Load Index From File Done. Skip BuildIndex.\n");
                return;
            }
            NORM_LOG("Index file missing or load failed. fallback to rebuild.\n");
        }
        // 走到这里有两种情况：
        // 1) force_build == true
        // 2) 没有成功命中两个索引文件
        index->BuildIndex(input_path);
        NORM_LOG("Saving ForwardIndex...\n");
        index->SaveForwardIndex(forward_index_path);
        index->SaveInvertedIndex(inverted_index_path);

    }
    
    //1.分词 2.获取文档ID——倒排拉链,去重 3.按权值排序 4.返回json
    void Search(const std::string &query, std::string *out_json){
        std::vector<std::string> query_words;
        util_ns::JiebaUtil::WordSplit(query, &query_words);

        std::vector<InvertedElement_dedup> all_invertedlist;
        std::unordered_map<std::uint64_t, InvertedElement_dedup> all_invertedlist_dedup;
        for(std::string word:query_words){
            boost::to_lower(word);
            std::vector<InvertedElement> *inverted_list = index->getInvertedList(word);
            if(inverted_list == nullptr) continue;
            //deduplicate
            for(const InvertedElement &elem : *inverted_list){
                InvertedElement_dedup& item = all_invertedlist_dedup[elem.id];
                item.id = elem.id;
                item.weight += elem.weight;
                item.word.push_back(elem.word);
            }
        }
        for(auto &item : all_invertedlist_dedup){
            DBG_LOG("push_back(): "); item.second.toStr();
            all_invertedlist.push_back(std::move(item.second));
        }
        std::sort(all_invertedlist.begin(), all_invertedlist.end(), 
        [](const InvertedElement_dedup &i1, const InvertedElement_dedup &i2){
            return i1.weight > i2.weight;
        });
        Json::Value root;
        for(const InvertedElement_dedup &elem : all_invertedlist){
            DocInfo *info = index->getDocInfo(elem.id);
            if(info == nullptr) continue;
            Json::Value item;
            item["title"] = info->title;
            item["desc"] = GetDesc(info->content, elem.word[0]);   // desc is a substr of content
            item["url"] = info->url;
            item["weight"] = elem.weight;
            root.append(item);
        }
        *out_json = Json::StyledWriter().write(root);
    }
    // 获取第一个word位置的前「prev_char」个和后「post_char」个词
    std::string GetDesc(const std::string &content, const std::string &word){
        // 用下面的接口 大小写有时候不匹配会找不到
        // ssize_t pos = content.find(word);
        const ssize_t prev_char = 100;
        const ssize_t post_char = 100;
        auto iter = std::search(content.begin(), content.end(), word.begin(), word.end(), [](int x, int y){
            return (std::tolower(x) == std::tolower(y));
        });
        if(iter == content.end()) return "NONE!!!";
        ssize_t pos = iter - content.begin();
        // 虽然不可能出现但是还是检测一下
        // if(pos == std::string::npos) return "NONE!!!";
        ssize_t begin = pos >= prev_char ? pos - prev_char : 0;
        ssize_t end = pos + post_char < content.size() ? pos + post_char : content.size();
        return content.substr(begin, end - begin);
    }
};